{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Deploy a Python model in Machine Learning Server with `azureml-model-management-sdk` Package "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "                               ***Applies to: Machine Learning Server 9.2***"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1. Read in the mtcars dataset\n",
    "\n",
    "From your local machine, let's begin by reading in the data we will use to build our model. We will use the dataset `mtcars`. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>car</th>\n",
       "      <th>mpg</th>\n",
       "      <th>cyl</th>\n",
       "      <th>disp</th>\n",
       "      <th>hp</th>\n",
       "      <th>drat</th>\n",
       "      <th>wt</th>\n",
       "      <th>qsec</th>\n",
       "      <th>vs</th>\n",
       "      <th>am</th>\n",
       "      <th>gear</th>\n",
       "      <th>carb</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>Mazda RX4</td>\n",
       "      <td>21.0</td>\n",
       "      <td>6</td>\n",
       "      <td>160.0</td>\n",
       "      <td>110</td>\n",
       "      <td>3.90</td>\n",
       "      <td>2.620</td>\n",
       "      <td>16.46</td>\n",
       "      <td>0</td>\n",
       "      <td>1</td>\n",
       "      <td>4</td>\n",
       "      <td>4</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>Mazda RX4 Wag</td>\n",
       "      <td>21.0</td>\n",
       "      <td>6</td>\n",
       "      <td>160.0</td>\n",
       "      <td>110</td>\n",
       "      <td>3.90</td>\n",
       "      <td>2.875</td>\n",
       "      <td>17.02</td>\n",
       "      <td>0</td>\n",
       "      <td>1</td>\n",
       "      <td>4</td>\n",
       "      <td>4</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>Datsun 710</td>\n",
       "      <td>22.8</td>\n",
       "      <td>4</td>\n",
       "      <td>108.0</td>\n",
       "      <td>93</td>\n",
       "      <td>3.85</td>\n",
       "      <td>2.320</td>\n",
       "      <td>18.61</td>\n",
       "      <td>1</td>\n",
       "      <td>1</td>\n",
       "      <td>4</td>\n",
       "      <td>1</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>Hornet 4 Drive</td>\n",
       "      <td>21.4</td>\n",
       "      <td>6</td>\n",
       "      <td>258.0</td>\n",
       "      <td>110</td>\n",
       "      <td>3.08</td>\n",
       "      <td>3.215</td>\n",
       "      <td>19.44</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "      <td>3</td>\n",
       "      <td>1</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>Hornet Sportabout</td>\n",
       "      <td>18.7</td>\n",
       "      <td>8</td>\n",
       "      <td>360.0</td>\n",
       "      <td>175</td>\n",
       "      <td>3.15</td>\n",
       "      <td>3.440</td>\n",
       "      <td>17.02</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>3</td>\n",
       "      <td>2</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                 car   mpg  cyl   disp   hp  drat     wt   qsec  vs  am  gear  \\\n",
       "0          Mazda RX4  21.0    6  160.0  110  3.90  2.620  16.46   0   1     4   \n",
       "1      Mazda RX4 Wag  21.0    6  160.0  110  3.90  2.875  17.02   0   1     4   \n",
       "2         Datsun 710  22.8    4  108.0   93  3.85  2.320  18.61   1   1     4   \n",
       "3     Hornet 4 Drive  21.4    6  258.0  110  3.08  3.215  19.44   1   0     3   \n",
       "4  Hornet Sportabout  18.7    8  360.0  175  3.15  3.440  17.02   0   0     3   \n",
       "\n",
       "   carb  \n",
       "0     4  \n",
       "1     4  \n",
       "2     1  \n",
       "3     1  \n",
       "4     2  "
      ]
     },
     "execution_count": 1,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# -- Import the dataset from the microsoftml package\n",
    "from microsoftml.datasets.datasets import DataSetMtCars\n",
    "mtcars = DataSetMtCars()\n",
    "\n",
    "# -- Represent the dataset as a dataframe.\n",
    "mtcars = mtcars.as_df()\n",
    "\n",
    "# -- print top rows of data to inspect the data\n",
    "mtcars.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 2. Authenticate and initiate the  `DeployClient`\n",
    "\n",
    "There are several ways to authentication against Machine Learning Server from your local machine. The method you choose should match the authentication defined by your administrator. Please contact your administrator for authentication credentials. \n",
    "\n",
    "For simplicity, this example uses the local 'admin' account for authentication.  \n",
    "\n",
    "1. Begin by importing the DeployClient and MLServer classes from the [azureml-model-management-sdk package](https://docs.microsoft.com/en-us/r-server/python-reference/azureml-model-management-sdk/azureml-model-management-sdk) so you can connect to Machine Learning Server (`use=MLServer`).\n",
    "\n",
    "1. Then, **fill in your own connection details** for the host and context into the corresponding fields. Learn more in the article \"[Connecting to Machine Learning Server in Python](https://docs.microsoft.com/en-us/r-server/operationalize/python/how-to-authenticate-in-python ).\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# -- Import the DeployClient and MLServer classes from the azureml-model-management-sdk package.\n",
    "from azureml.deploy import DeployClient\n",
    "from azureml.deploy.server import MLServer\n",
    "\n",
    "# -- Define the location of the ML Server --\n",
    "# -- for local onebox for Machine Learning Server: http://localhost:12800\n",
    "# -- Replace with connection details to your instance of ML Server. \n",
    "HOST = 'http://localhost:12800'\n",
    "context = ('admin', 'YOUR_ADMIN_PASSWORD')\n",
    "client = DeployClient(HOST, use=MLServer, auth=context)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You are now authenticated. \n",
    "\n",
    "The **DeployClient** can interact with the web service management APIs to deploy, list, consume and so on. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 3. Create and run a linear model locally\n",
    "\n",
    "Now that you have built the authentication logic into your application, you can interact with the core APIs using functions in azureml-model-management-sdk to create a model and publish it as a web service.\n",
    "\n",
    "Create a GLM model called `cars_model` using the dataset `mtcars` we imported before. Using horsepower (hp) and weight (wt), this model estimates the probability that a vehicle has been fitted with a manual transmission.\n",
    "\n",
    "We use the [rx_lin_mod](https://docs.microsoft.com/en-us/r-server/python-reference/revoscalepy/rx-lin-mod) function from the [revoscalepy package](https://docs.microsoft.com/en-us/r-server/python-reference/revoscalepy/revoscalepy-package) to build the model."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Rows Read: 32, Total Rows Processed: 32, Total Chunk Time: Less than .001 seconds \n",
      "Computation time: 0.005 seconds.\n",
      "Rows Read: 1, Total Rows Processed: 1, Total Chunk Time: Less than .001 seconds \n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>am_Pred</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>0.533267</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "    am_Pred\n",
       "0  0.533267"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# -- import the needed classes and functions\n",
    "import pandas as pd\n",
    "from revoscalepy import rx_lin_mod, rx_predict\n",
    "\n",
    "# -- using rx_lin_mod from revoscalepy package\n",
    "# -- create glm model with `mtcars` dataset\n",
    "cars_model = rx_lin_mod(\n",
    "    formula='am ~ hp + wt',\n",
    "    data=mtcars)\n",
    "\n",
    "# -- provide some sample inputs to test the model\n",
    "mydata = pd.DataFrame({\n",
    "    'hp':[120],\n",
    "    'wt':[2.8]\n",
    "})\n",
    "mydata\n",
    "\n",
    "# -- predict locally\n",
    "rx_predict(cars_model, data=mydata)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Examine the results of the locally executed code. You can compare these results to the results of the web service in this next step."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 4. Publish the model as a web service\n",
    "\n",
    "Now let's:\n",
    "+ Define an initialization function \n",
    "+ Produce a prediction function\n",
    "+ Publish the linear model as a Python web service\n",
    "\n",
    "You can define an 'init' function to be bootstrapped to the web service. This 'init' function handles service initialization. Use it to load the packages, datasets, and global variables you need when the service is called the first time. One caveat, however, is that all imports are scoped to the 'init' function and not to the global namespace. Consequently, you must still import the modules in each run or consume function. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# --Define an `init` function to handle service initialization --\n",
    "def init():\n",
    "    import pandas as pd\n",
    "    from revoscalepy import rx_predict\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Produce a prediction function called `manualTransmission` that can use the `cars_model` model. This function will be part of the code `code_fn` supplied when the service is published."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def manualTransmission(hp, wt):\n",
    "    import pandas as pd\n",
    "    from revoscalepy import rx_predict\n",
    "    \n",
    "    # -- make the prediction use model `cars_model` and input data --\n",
    "    newData = pd.DataFrame({'hp':[hp], 'wt':[wt]})\n",
    "    answer = rx_predict(cars_model, newData, type='response')\n",
    "    \n",
    "    # -- save some files to demonstrate the ability to return file artifacts --\n",
    "    answer.to_csv('answer.csv')\n",
    "    # return prediction\n",
    "    return answer"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now we can publish the model as a web service called `\"cars_model\"` to Machine Learning Server. This service uses the model `carsModel`, the function `manualTransmission`, and the `init` function. As an input, the service takes a list of vehicle horsepower and vehicle weight represented as a float. As an output, a percentage as a dataframe for the probability each vehicle has of being fitted with a manual transmission.\n",
    "\n",
    "When publishing a service, you can specify its name and version, the code functions, the inputs, and the outputs needed for application integration as well as other parameters. \n",
    "\n",
    "NOTE ON THE BASICS OF SERVICE PUBLISHING API:\n",
    "\n",
    "The fluent APIS are designed for optional configurations where the readability of the invocation is close to that of the ordinary written prose (grammatical structure). A publish can be initiated by invoking the `client.service(name)` object then calling `.deploy()`  to send the publish request."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "service_name = 'TxService'\n",
    "\n",
    "service = client.service(service_name)\\\n",
    "        .version('1.0')\\\n",
    "        .code_fn(manualTransmission, init)\\\n",
    "        .inputs(hp=float, wt=float)\\\n",
    "        .outputs(answer=pd.DataFrame)\\\n",
    "        .models(cars_model=cars_model)\\\n",
    "        .description('My first python model')\\\n",
    "        .artifacts(['answer.csv'])\\\n",
    "        .deploy()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 5. Explore and consume the published web service\n",
    "\n",
    "Now let's:\n",
    "+ Use the help function to explore the published service. You can call the `help` function on any `azureml-model-management-sdk` functions, even those we dynamically generated ones to learn more about them.\n",
    "\n",
    "+ Print the capabilities that define the service holdings: service name, version, descriptions, inputs, outputs, and the name of the function to be consumed.\n",
    "\n",
    "+ Predict an outcome.\n",
    "\n",
    "+ Download the Swagger-based JSON file.  This file is auto-generated at deploy time.  You can share it with any authenticated users so they can test and consume the service.   **You can share the resulting file with application developers or others testing your service.** Learn more about [exploring and consuming in this notebook](https://github.com/Microsoft/ML-Server-Python-Samples/blob/master/web-services/deploy-consume/Explore_Consume_Python_Web_Services.ipynb).\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Help on TxserviceService in module azureml.deploy.server.service object:\n",
      "\n",
      "class TxserviceService(Service)\n",
      " |  Service object from metadata.\n",
      " |  \n",
      " |  Method resolution order:\n",
      " |      TxserviceService\n",
      " |      Service\n",
      " |      builtins.object\n",
      " |  \n",
      " |  Methods defined here:\n",
      " |  \n",
      " |  __init__(self, service, http_client)\n",
      " |      Constructor\n",
      " |      \n",
      " |      :param service:\n",
      " |      :param http_client:\n",
      " |  \n",
      " |  __str__(self)\n",
      " |      Return str(self).\n",
      " |  \n",
      " |  batch(self, records, parallel_count=10)\n",
      " |      Register a set of input records for batch execution on this service.\n",
      " |      \n",
      " |      :param records: The `data.frame` or `list` of\n",
      " |             input records to execute.\n",
      " |      :param parallel_count: Number of threads used to process entries in\n",
      " |             the batch. Default value is 10. Please make sure not to use too\n",
      " |             high of a number because it might negatively impact performance.\n",
      " |      :return: The `Batch` object to control service batching\n",
      " |              lifecycle.\n",
      " |  \n",
      " |  capabilities(self)\n",
      " |      Gets the service holding capabilities.\n",
      " |      \n",
      " |      :return: A dict of key/values describing the service.\n",
      " |  \n",
      " |  get_batch(self, execution_id)\n",
      " |      Retrieve the `Batch` based on an `execution id`\n",
      " |      \n",
      " |      :param execution_id: The id of the batch execution.\n",
      " |      :return: The `Batch`.\n",
      " |  \n",
      " |  list_batch_executions(self)\n",
      " |      Gets all batch executions currently queued for this service.\n",
      " |      \n",
      " |      :return: A list of `execution ids`.\n",
      " |  \n",
      " |  manualTransmission(self, hp, wt)\n",
      " |      Consume the TxService service.\n",
      " |      \n",
      " |      My first python model\n",
      " |      \n",
      " |      :param float hp: The required service input.\n",
      " |      :param float wt: The required service input.    \n",
      " |      :returns ServiceResponse: The `<ServiceResponse>` object contains the set of\n",
      " |          expected output values and artifacts. The possible outputs include:\n",
      " |                  \n",
      " |          Output: pandas.DataFrame answer        \n",
      " |      \n",
      " |      :Raises:\n",
      " |          HttpException: If server errors occur while executing the service.\n",
      " |          ValueError: If argument input types do not match the expected service\n",
      " |              input types.\n",
      " |  \n",
      " |  swagger(self)\n",
      " |      Retrieves the `swagger.json` for this service (see http://swagger.io/).\n",
      " |      :return: The swagger document for this service.\n",
      " |  \n",
      " |  ----------------------------------------------------------------------\n",
      " |  Static methods defined here:\n",
      " |  \n",
      " |  __new__(cls, *args, **kwargs)\n",
      " |      Create and return a new object.  See help(type) for accurate signature.\n",
      " |  \n",
      " |  ----------------------------------------------------------------------\n",
      " |  Data descriptors defined here:\n",
      " |  \n",
      " |  __dict__\n",
      " |      dictionary for instance variables (if defined)\n",
      " |  \n",
      " |  __weakref__\n",
      " |      list of weak references to the object (if defined)\n",
      "\n",
      "None\n"
     ]
    }
   ],
   "source": [
    "print(help(service))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Explore all available functions on the service object by calling `capabilities`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'api': '/api/TxService/1.0',\n",
       " 'artifacts': ['answer.csv', 'image.png'],\n",
       " 'creation_time': '2017-09-29T17:55:13.5336083',\n",
       " 'description': 'My first python model',\n",
       " 'inputs': [{'name': 'hp', 'type': 'numeric'},\n",
       "  {'name': 'wt', 'type': 'numeric'}],\n",
       " 'inputs_encoded': [{'name': 'hp', 'type': 'float'},\n",
       "  {'name': 'wt', 'type': 'float'}],\n",
       " 'name': 'TxService',\n",
       " 'operation_id': 'manualTransmission',\n",
       " 'outputs': [{'name': 'answer', 'type': 'data.frame'}],\n",
       " 'outputs_encoded': [{'name': 'answer', 'type': 'pandas.DataFrame'}],\n",
       " 'public-functions': {'batch': 'batch(records, parallel_count=10)',\n",
       "  'capabilities': 'capabilities()',\n",
       "  'get_batch': 'get_batch(execution_id)',\n",
       "  'list_batch_execution': 'list_batch_execution()',\n",
       "  'manualTransmission': 'manualTransmission(self,hp,wt)',\n",
       "  'swagger': 'swagger(json=True)'},\n",
       " 'published_by': 'admin',\n",
       " 'runtime': 'Python',\n",
       " 'snapshot_id': '9070c737-645e-42bf-a300-5e8f97d14c22',\n",
       " 'swagger': 'http://localhost:12800/api/TxService/1.0/swagger.json',\n",
       " 'version': '1.0'}"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "service.capabilities()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Since you are in the same session as the one you in which you deployed, you can consume it using the _service api_ object returned from `.deploy()`. You can verify that the results are as expected and that they match the results obtained when the model was run locally earlier. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "    am_Pred\n",
      "0  0.533267\n"
     ]
    }
   ],
   "source": [
    "res = service.manualTransmission(120, 2.8)\n",
    "\n",
    "# -- Pluck out the named output `answer` as defined during publishing and print --\n",
    "print(res.output('answer'))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now you can grab the Swagger-based JSON file, which defines the service."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "http://localhost:12800/api/TxService/1.0/swagger.json\n"
     ]
    }
   ],
   "source": [
    "# -- Retrieve the URL of the swagger file for this service.\n",
    "cap = service.capabilities()\n",
    "swagger_URL = cap['swagger']\n",
    "print(swagger_URL)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Learn more about how to [list, get, explore and consume web services in this notebook](https://github.com/Microsoft/ML-Server-Python-Samples/blob/master/operationalize/Explore_Consume_Python_Web_Services.ipynb).\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 6. Delete services\n",
    "\n",
    "You can call `delete_service` on the `DeployClient` object to delete a specific service on the running Machine Learning Server."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 16,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "client.delete_service('TxService', version='1.0')"
   ]
  }
 ],
 "metadata": {
  "anaconda-cloud": {},
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.5.1"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
